-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new feature resolver. #7820
Conversation
(rust_highfive has picked a reviewer for you, use r? to override) |
☔ The latest upstream changes (presumably #7731) made this pull request unmergeable. Please resolve the merge conflicts. |
3eeda09
to
68304c2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I've left a bunch of initial comments below, although I didn't get a chance to finish today unfortunately. Wanted to give you some time to respond though since I may be busy for a day or so. The major thing I didn't get finished reviewing is the internals of the new implementation of feature resolution, which I suspect would lift some confusion I have about DepKind
, so feel free to defer to those internals if you'd like.
While looking through some of your questions, I found a bug with I'll try to fix that, and then respond to your questions (and try to add some comments to clarify confusing sections). |
So I think there may need to be significant changes, and I wanted to confirm that my assumptions make sense. I'll illustrate with an example project with these dependencies: [dependencies]
dep = {version="1.0", features=["f1"]}
[dev-dependencies]
dep = {version="1.0", features=["f2"]} With:
(Hopefully that's pretty straightforward.) The tricky part comes with This causes If you agree with that, then the tricky bit comes with how to build that graph. The only difference for the |
So that makes sense to me if we take into account |
Yea, I'm not really clear on what the best behavior here is. How about this: have some sort of internal boolean ( I'm not sure if that might be too confusing or surprising. Perhaps with documentation it can be made clear? I'm inclined to go that direction because it will be much simpler to implement. If we decide that dependencies should be "forked" and built multiple times with different features, we can add that in the future. How does that sound? |
That sounds relatively reasonable to me yeah, the difference in |
☔ The latest upstream changes (presumably #7851) made this pull request unmergeable. Please resolve the merge conflicts. |
68304c2
to
61f73a6
Compare
I did a fairly major rewrite to try to simplify things. Now that dev-dependency unification is a global setting, it all really boils down to whether or not something is a build dependency, so I just made that a bool. It's not as flexible, but I can always switch it back to something more complex if needed. I also add BTW, I'd like to squash this before merging, so don't merge as-is. This also now includes #7857 to fix some issues, but that PR should probably go first to simplify this. |
Does this PR also address #2589 and #2524 ? If not I'd try to look deeper into the 2589 myself as it would be quite convenient to have something like
This example is actually much simpler than what you try to solve cause dependency graph just need a replacement of the node (actually just a path/version/git) based on target, not a feature, but may be it's also fixed as a by-product? |
Ok, thanks for an answer. Another short question: if paths are not easy to fix, may be it would be able to introduce a syntax like
or something similar. If it ever makes sense. At least it's a short-term fix for different paths problem. |
That is discussed in #1197. |
☔ The latest upstream changes (presumably #7855) made this pull request unmergeable. Please resolve the merge conflicts. |
f07d254
to
ae57a25
Compare
Fix BuildScriptOutput when a build script is run multiple times. When I implemented profile build overrides, I created a scenario where a build script could run more than once for different scenarios. See the test for an example. However, the `BuildScriptOutputs` map did not take this into consideration. This caused multiple build script runs to stomp on the output of previous runs. This is further exacerbated by the new feature resolver in #7820 where build scripts can run with different features enabled. The solution is to make the map key unique for the Unit it is running for. Since this map must be updated on different threads, `Unit` cannot be used as a key, so I chose just using the metadata hash which is hopefully unique. Most of this patch is involved with the fussiness of getting the correct metadata hash (we want the RunCustomBuild unit's hash). I also added some checks to avoid collisions and assert assumptions.
Ok this is taking me longer to get to reviewing than I thought it might. I'll be gone most of next week as well. From my previous review and given the changes you've described implementing, I'd be fine landing this in the meantime and I can comment on this when I get a chance to. Or if you're ok waiting a week or so that's ok too! |
I guess I can say a thing or two about IntelliJ and rust-analyzer, who are perhaps the two biggest users of Specifically, Resolve is on the level of packages, but what rust-analyzer & IntelliJ want is more close to units. For example, we would like to see "crate ./test/foo.rs depends on crate ./src/lib.rs with these cfg flags set". I think we can make two steps to make sure that things "make sense".
|
@matklad I've been thinking the exact same thing. I've also been wondering if we could just extend I'd really like a tool to visualize the unit graph, but that is difficult for any non-trivial project. |
FYI cargo-guppy relies on metadata as well: https://github.com/calibra/cargo-guppy |
ae57a25
to
57867e7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok apologies again for the delay in getting to review this.
This time around though I was able to process it a whole lot faster! The current logic makes sense to me, especially how dev-dependencies and their features are handled. I think what's implemented probably won't last until the end of time but it's certainly a helluva lot better than what we have now and is worth getting stabilized (IMO)
for fv in fvs { | ||
self.activate_fv(pkg_id, fv, for_build)?; | ||
} | ||
if !self.processed_deps.insert((pkg_id, for_build)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This guard seems a bit suspicious to me. Just because we visited a package before here doesn't mean we didn't later add features which themselves add dependencies, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking of something like cargo build -p deep-dependency -p root-dependency
where it activates deep-dependency
quickly but by activating root-dependency
we transitively enable more features of deep-dependency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is fine. After resolving deep-dependency once, other packages that add features to that deep-dependency will do so via FeatureValue::CrateFeature
in activate_fv
. This in turn checks if it is activating an optional dependency, and will recurse into that if necessary.
This is kinda fundamental to the way this is organized. If you notice, a few lines below it always skips optional dependencies. Whenever a dependency is encountered that enables an optional feature, it will enable it and recurse right away.
I believe this is structured somewhat differently from how the dependency resolver works. Presumably because it is figuring out the features as it goes along, and doesn't have the full graph built, yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok the skipping of optional deps below actually does make this sense yeah, sorry I missed that! It still feels a bit funky to me but because it's doing the skip I'm fine deferring to tests to verify the correctness of this. Mind adding a comment here clarifying what sort of recursion this is guarding against, and how more recursion is actually allowed via feature activation below in a limited way?
/// features of the non-host package (whereas `host` is true because the | ||
/// build script is being built for the host). `build_dep` becomes `true` | ||
/// for build-dependencies, or any of their dependencies. | ||
build_dep: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking about this for a bit now. I understand (I think?) all the words in this comment, but I'm having trouble piecing together a situation about where this becomes relevant. Can you give a small example about where this subtle distinction is necessary?
Additionally, since host=false, build_dep=true
probably doesn't make much sense, would it make more sense for this to be some sort of tri-value thing like:
enum Kind {
Target,
Host { build_dep: bool },
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(please don't use Kind
as I'm sure you're already thinking when reading that)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added an example.
I'm reluctant to add an enum to represent the 3 states. All the code is oriented around simple booleans, and I think it would make it harder to follow if they were unified into one field. I usually agree that it is good to prevent impossible scenarios, but in this case I think it might be harder to understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok the example looks good to me, thanks! I'm also fine deferring to your thoughts about booleans!
One final question here, though. Technically we have two sets of fetures to deal with, one when the build script is compiled (the --feature
flags) and one when it's executed (CARGO_FEATURE_*
). Given that is_custom_build
is conflating both executing and compiling the build script, I think we may want to be a little more nuanced/precise? I would expect that the compilation of the build script would use the host: true
and build_dep: false
set of features, but the execution of the build script would use host: false
and build_dep: false
as well, right? (ok maybe this doesn't have to do with build_dep
?)
Also to clarify, in the example you listed here, shared_dep build.rs
is only compiled once regardless of feature settings, right? And then it's executed twice maybe depending on feature settings/targets?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, so it is working correctly, but you do point out that host
isn't accurate for the RunCustomBuild unit. host
only matters for the profile, and the RunCustomBuild unit goes through a separate code path for computing the profile. That is here where it manually fetches the profile (ignoring unit_for). All other units go through this path where it inspects unit_for to determine the profile. However, it is necessary for host
to be true
for RunCustomBuild so that all dependencies below it are marked as "host:true". Added some comments explaining this.
shared_dep build.rs
is only compiled once regardless of feature settings, right? And then it's executed twice maybe depending on feature settings/targets?
It depends. With -Zfeatures=build_dep
, and the features listed are different between the normal and build dep, then it will get built twice. The reason for this is that many scripts out there use #[cfg]
and cfg!
to detect features, instead of inspecting "CARGO_FEATURE_*". Only building it once would break a large number of build scripts. I don't think it would be feasible to avoid that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok that makes sense, I know I've "used and abused" that CARGO_FEATURE_*
is the "same" as #[cfg]
in the past myself, so having a special case for this makes sense.
/// | ||
/// This is part of the machinery responsible for handling feature | ||
/// decoupling for build dependencies in the new feature resolver. | ||
pub fn with_build_dep(mut self, build_dep: bool) -> UnitFor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a minor nuance (and this was already present, just something I'm realizing now) the terminology of with_*
doesn't quite fit here in my mind since I'd expect a function of that name to unconditionally configure build_dep
with the argument provided, but here it's more of a unioning function. I'm not sure of a better name though!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I struggled a bit with the naming convention and wasn't happy with it. But I'm unable to think of anything particularly better. If you think of something, let me know!
I don't know if there is a common naming convention for this idiom (creating a copy and modifying a field in one step)? Maybe that's a sign it may be better to be explicit about creating a copy, and then call set_build_dep
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eh it's a minor thing, I wouldn't stress out much over it. I definitely know of no naming convention for this, so it may be good to just highlight it in the documentation and move on. (the current call-sites all "look right" which is what matters)
Update cargo 11 commits in e02974078a692d7484f510eaec0e88d1b6cc0203..e57bd02999c9f40d52116e0beca7d1dccb0643de 2020-02-18 15:24:43 +0000 to 2020-02-21 20:20:10 +0000 - fix most remaining clippy findings (mostly redundant imports) (rust-lang/cargo#7912) - Add -Zfeatures tracking issues. (rust-lang/cargo#7917) - Use rust-lang/rust linkchecker on CI. (rust-lang/cargo#7913) - Clean up code mostly based on clippy suggestions (rust-lang/cargo#7911) - Add an option to include crate versions to the generated docs (rust-lang/cargo#7903) - Better support for license-file. (rust-lang/cargo#7905) - Add new feature resolver. (rust-lang/cargo#7820) - Switch azure to macOS 10.15. (rust-lang/cargo#7906) - Modified the help information of cargo-rustc (rust-lang/cargo#7892) - Update for nightly rustfmt. (rust-lang/cargo#7904) - Support `--config path_to_config.toml` cli syntax. (rust-lang/cargo#7901)
Thank you so so much!!! |
The [new cargo feature resolver](rust-lang/cargo#7820) won't unify features across build deps, dev deps, and targets. This solves our problem of needing to work around feature unification to avoid Clone implementations on private key material. This commit puts back our true dependency information into the Cargo.tomls. Specifically, it adds dev-dependencies that enable features that aren't enabled on normal dependencies. This will cause the feature unification to happen when using the old resolver, but the build will be correct under the new resolver. In order to maintain correct builds in CI, this commit also changes CI to use the nightly cargo with `-Z features=all` but still preserving the rustc toolchain specified in `rustc-toolchain`. Local developer builds will likely still use the `rustc-toolchain` version of cargo with the old resolver, but this shouldn't cause any problems for development.
The [new cargo feature resolver](rust-lang/cargo#7820) won't unify features across build deps, dev deps, and targets. This solves our problem of needing to work around feature unification to avoid Clone implementations on private key material. This commit puts back our true dependency information into the Cargo.tomls. Specifically, it adds dev-dependencies that enable features that aren't enabled on normal dependencies. This will cause the feature unification to happen when using the old resolver, but the build will be correct under the new resolver. In order to maintain correct builds in CI, this commit also changes CI to use the nightly cargo with `-Z features=all` but still preserving the rustc toolchain specified in `rustc-toolchain`. Local developer builds will likely still use the `rustc-toolchain` version of cargo with the old resolver, but this shouldn't cause any problems for development.
The [new cargo feature resolver](rust-lang/cargo#7820) won't unify features across build deps, dev deps, and targets. This solves our problem of needing to work around feature unification to avoid Clone implementations on private key material. This commit puts back our true dependency information into the Cargo.tomls. Specifically, it adds dev-dependencies that enable features that aren't enabled on normal dependencies. This will cause the feature unification to happen when using the old resolver, but the build will be correct under the new resolver. In order to maintain correct builds in CI, this commit also changes CI to use the nightly cargo with `-Z features=all` but still preserving the rustc toolchain specified in `rustc-toolchain`. Local developer builds will likely still use the `rustc-toolchain` version of cargo with the old resolver, but this shouldn't cause any problems for development.
The [new cargo feature resolver](rust-lang/cargo#7820) won't unify features across build deps, dev deps, and targets. This solves our problem of needing to work around feature unification to avoid Clone implementations on private key material. This commit puts back our true dependency information into the Cargo.tomls. Specifically, it adds dev-dependencies that enable features that aren't enabled on normal dependencies. This will cause the feature unification to happen when using the old resolver, but the build will be correct under the new resolver. In order to maintain correct builds in CI, this commit also changes CI to use the nightly cargo with `-Z features=all` but still preserving the rustc toolchain specified in `rustc-toolchain`. Local developer builds will likely still use the `rustc-toolchain` version of cargo with the old resolver, but this shouldn't cause any problems for development.
The [new cargo feature resolver](rust-lang/cargo#7820) won't unify features across build deps, dev deps, and targets. This solves our problem of needing to work around feature unification to avoid Clone implementations on private key material. This commit puts back our true dependency information into the Cargo.tomls. Specifically, it adds dev-dependencies that enable features that aren't enabled on normal dependencies. This will cause the feature unification to happen when using the old resolver, but the build will be correct under the new resolver. In order to maintain correct builds in CI, this commit also changes CI to use the nightly cargo with `-Z features=all` but still preserving the rustc toolchain specified in `rustc-toolchain`. Local developer builds will likely still use the `rustc-toolchain` version of cargo with the old resolver, but this shouldn't cause any problems for development.
The [new cargo feature resolver](rust-lang/cargo#7820) won't unify features across build deps, dev deps, and targets. This solves our problem of needing to work around feature unification to avoid Clone implementations on private key material. This commit puts back our true dependency information into the Cargo.tomls. Specifically, it adds dev-dependencies that enable features that aren't enabled on normal dependencies. This will cause the feature unification to happen when using the old resolver, but the build will be correct under the new resolver. In order to maintain correct builds in CI, this commit also changes CI to use the nightly cargo with `-Z features=all` but still preserving the rustc toolchain specified in `rustc-toolchain`. Local developer builds will likely still use the `rustc-toolchain` version of cargo with the old resolver, but this shouldn't cause any problems for development.
The [new cargo feature resolver](rust-lang/cargo#7820) won't unify features across build deps, dev deps, and targets. This solves our problem of needing to work around feature unification to avoid Clone implementations on private key material. This commit puts back our true dependency information into the Cargo.tomls. Specifically, it adds dev-dependencies that enable features that aren't enabled on normal dependencies. This will cause the feature unification to happen when using the old resolver, but the build will be correct under the new resolver. In order to maintain correct builds in CI, this commit also changes CI to use the nightly cargo with `-Z features=all` but still preserving the rustc toolchain specified in `rustc-toolchain`. Local developer builds will likely still use the `rustc-toolchain` version of cargo with the old resolver, but this shouldn't cause any problems for development.
The [new cargo feature resolver](rust-lang/cargo#7820) won't unify features across build deps, dev deps, and targets. This solves our problem of needing to work around feature unification to avoid Clone implementations on private key material. This commit puts back our true dependency information into the Cargo.tomls. Specifically, it adds dev-dependencies that enable features that aren't enabled on normal dependencies. This will cause the feature unification to happen when using the old resolver, but the build will be correct under the new resolver. In order to maintain correct builds in CI, this commit also changes CI to use the nightly cargo with `-Z features=all` but still preserving the rustc toolchain specified in `rustc-toolchain`. Local developer builds will likely still use the `rustc-toolchain` version of cargo with the old resolver, but this shouldn't cause any problems for development.
This adds a new resolver which handles feature unification independently of the main resolver. This can be enabled with the
-Zfeatures
flag which takes a comma-separated list of options to enable new behaviors. Seeunstable.md
docs for details.There are two significant behavior changes:
The "forks" in the unit graph are handled by adding
DepKind
toUnitFor
. The feature resolver tracks features independently for the different dependency kinds.Unfortunately this currently does not support decoupling proc_macro dependencies. This is because at resolve time it does not know which dependencies are proc_macros. Moving feature resolution to after the packages are downloaded would require massive changes, and would make the unit computation much more complex. Nobody to my knowledge has requested this capability, presumably because proc_macros are relatively new, and they tend not to have very many dependencies, and those dependencies tend to be proc-macro specific (like syn/quote). I'd like to lean towards adding proc-macro to the index so that it can be known during resolve time, which would be much easier to implement, but with the downside of needing to add a new field to the index.
I did not update
cargo metadata
, yet. It's not really clear how it should behave. I think I'll need to investigate how people are currently using the feature information and figure out how it should work. Perhaps adding features to "dep_kinds" will be the solution, but I'm not sure.The goal is to try to gather feedback about how well this new resolver works. There are two important things to check: whether it breaks a project, and how much does it increases compile time (since packages can be built multiple times with different features). I'd like to stabilize it one piece at a time assuming the disruption is not too great. If a project breaks or builds slower, the user can implement a backwards-compatible workaround of sprinkling additional features into
Cargo.toml
dependencies. I thinkitarget
is a good candidate to try to stabilize first, since it is less likely to break things or change how things are built. If it does cause too much disruption, then I think we'll need to consider making it optional, enabled somehow.There is an environment variable that can be set which forces Cargo to use the new feature resolver. This can be used in Cargo's own testsuite to explore which tests behave differently with the different features set.